/*
This software is distributed under the Simplified BSD License:

Copyright (c) 2008, Chris Venter <chris.venter@gmail.com>
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, 
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice, 
    	this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright notice, 
    	this list of conditions and the following disclaimer in the documentation 
    	and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 
SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 
DAMAGE.
*/
#ifndef GENERIK_GSTREAMER_H
#define GENERIK_GSTREAMER_H

/** \file gstreamer.h
This file contains the gstreamer implementation of the generik::media::player
interface.
*/

#include <gst/gst.h>
#include "media.h"
#include "stringutils.h"

#include "tracing.h"

namespace generik
{
	
	namespace media
	{

		/// gstreamer-implementation of the generik::media::player interface
		/** This is a %gstreamer (http://www.gstreamer.net/) based implementation
		of the player interface.
		
		Depending on the installed %gstreamer plugins available on the target machine, this
		%player will allow almost any %media format to be played back.
		*/
		struct gstreamer
		: virtual player
		{
			/// the currently playing file
			std::string _location;

			/// the gstreamer playbin element
			GstElement* _play;
			/// the bus for _play
			/** This lets _play run in its own thread, thereby de-coupling playback
			from any other action which runs gst_player. Neat!
			*/
			GstBus*		_bus;

			/// the status of gst_player is stored here
			state _status;
			/// any verbose status messages (such as errors) are stored here
			std::string _statusmsg;

			bool _running;
			pthread_t update_thread;

			/// constructor
			/** Sets up gstreamer and the playbin element in its own bus, and initialises it
			*/
			gstreamer()
			: _location("")
			, _play(0), _bus(0)
			, _status(GMP_STOP)
			, _statusmsg("")
			{
				GENERIK_TRACER; //_SILENT;

				// init GStreamer
				gst_init(0, 0);
				// force the debug level to be zero, since gstreamer outputs junk on stdout.
				gst_debug_set_default_threshold(GST_LEVEL_NONE);

				// build the playbin element
				_play = gst_element_factory_make("playbin", "play");
				GENERIK_ASSERT(_play);
				// create a pipeline bus for the playbin element
				_bus = gst_pipeline_get_bus(GST_PIPELINE(_play));
				GENERIK_ASSERT(_bus);

				// why do we do this?
				gst_object_unref(_bus);

				_running = true;
				pthread_create(&update_thread, 0, &update, this);

				// default state of the player
				stop();
			}
			/// destructor
			/** Cleans up the gst elements we have used
			*/
			~gstreamer()
			{
				GENERIK_TRACER;

				exit_update();
				//pthread_join(update_thread, 0);

				gst_element_set_state(_play, GST_STATE_NULL);
				gst_object_unref(_play);
			}

			/// opens the specified filename
			/** filename may be a uri, in which case we just open it.
			If it is not, we prepend the "file://" string to the filename
			before opening, which implicitly assumes that we are attempting
			to open a local file
			*/
			void open(const std::string& filename_in)
			{
				GENERIK_TRACER; //_SILENT;

				_location = filename_in;
				open();
			}
			void open()
			{
				GENERIK_TRACER;
				// we should check wether the filename is already a uri.
				// if so, simply use it. If not, prepend the file:// base
				std::string uri = _location;
				if (!uri.empty())
				{
					if (!isa_uri(_location))
					{
						const std::string url_prefix = "file://";
						uri = url_prefix + uri;
					}

					GENERIK_TRACE_VALUE(uri);
					g_object_set(G_OBJECT(_play), "uri", uri.c_str(), NULL);
				}
				else
				{
					GENERIK_TRACE("uri is empty");
				}
			}
			/// starts playback
			void play()
			{
				GENERIK_TRACER; //_SILENT;
				_status = GMP_PLAY;
				_statusmsg = "";
				gst_element_set_state(_play, GST_STATE_PLAYING);
			}
			/// pauses playback
			void pause()
			{
				GENERIK_TRACER; //_SILENT;
				_status = GMP_PAUSE;
				_statusmsg = "";
				gst_element_set_state(_play, GST_STATE_PAUSED);
			}
			/// stops playback
			void stop()
			{
				GENERIK_TRACER; //_SILENT;
				_status = GMP_STOP;
				_statusmsg = "";
				gst_element_set_state(_play, GST_STATE_NULL);
			}
			/// skips forward by 20 seconds
			void fwd()
			{
				GENERIK_TRACER; //_SILENT;
				seekrel(+20);
			}
			/// skips back by 20 seconds
			void rew()
			{
				GENERIK_TRACER; //_SILENT;
				seekrel(-20);
			}
			/// seeks a specified number of seconds relative to the current position
			void seekrel(int seconds)
			{
				GENERIK_TRACER; //_SILENT;
				// since gst_element_seek expects nanoseconds, we need to adjust for seconds
				gint64 offset = (gint64)(seconds * 1e9);
				gint64 position = pos();
				gint64 new_pos = position + (guint64)offset;
				gst_element_seek(_play, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH,
					 GST_SEEK_TYPE_SET, new_pos,
					 GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
			}
			/// seeks to a specific offset (in seconds) from the start of the track
			void seek(int seconds)
			{
				GENERIK_TRACER; //_SILENT;
				// since gst_element_seek expects nanoseconds, we need to adjust for seconds
				gint64 offset = (gint64)(seconds * 1e9);
				gst_element_seek(_play, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH,
					 GST_SEEK_TYPE_SET, offset,
					 GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
			}
			/// returns the length of the current file, in nanoseconds (?)
			uint64 len()
			{
				REM_GENERIK_TRACER; //_SILENT;
				gint64 length = 0;
				GstFormat fmt = GST_FORMAT_TIME;
				gst_element_query_duration(_play, &fmt, &length);
				return length;
			}
			/// returns the current playback position of the current file, in nanoseconds (?)
			uint64 pos()
			{
				REM_GENERIK_TRACER; //_SILENT;
				gint64 position = 0;
				GstFormat fmt = GST_FORMAT_TIME;
				gst_element_query_position(_play, &fmt, &position);
				return position;
			}
			/// returns the status of the player
			state status()
			{
				REM_GENERIK_TRACER; //_SILENT;
				GENERIK_TRACE_VALUE(_status);
				return _status;
			}
			/// returns the verbose status of the player
			/** Useful for finding the error message when status() returns ERROR.
			*/
			std::string statusmsg()
			{
				GENERIK_TRACER; //_SILENT;
				GENERIK_TRACE_VALUE(_statusmsg);
				return _statusmsg;
			}

			void run()
			{
				GENERIK_TRACER;
				play();
				wait_update();
				//pthread_join(update_thread, 0);
				/*
				while (status() == PLAY)
				{
					//_update();
					usleep(32*1024);
				}
				*/
			}

			void wait_update()
			{
				GENERIK_TRACER;
				pthread_join(update_thread, 0);
			}

			void exit_update()
			{
				GENERIK_TRACER;
				_running = false;
				wait_update();
			}

			static void* update(void* object)
			{
				GENERIK_TRACER;
				gstreamer* player = (gstreamer*)(object);
				player->update();
				return 0;
			}
			void update()
			{
				GENERIK_TRACER;
				while (_running)
				{
					usleep(32*1024);
					_update();
				}
			}

			/// asynchronous update method
			/** This method checks the gst bus for any messages emenating from the
			playbin element, and takes action accordingly.
			Note that we are only interested in errors and end-of-streams in this
			implementation.
			*/
			// needs to be: bool _update()
			void _update()
			{
				//REM_GENERIK_TRACER;
				//
				// this needs to be implemented, to keep track of wether or not the player is running or not.
				// it is run in a thread by the player itself, and should therefore exit by itself once the player stops/exits.
				// creation/running of this thread should be mutex protected?
				// Also, any access to the player should ideally be mutex protected, since this thread may access the player
				// while a client is busy doing so!
				//
				// process all waiting/pending messages
				while (gst_bus_have_pending(_bus))
				{
					GstMessage* message = 0;
					// retrieve the message from the bus
					message = gst_bus_pop(_bus);
					if (message)
					{
						switch (GST_MESSAGE_TYPE(message))
						{
						case GST_MESSAGE_ERROR: {
							GENERIK_TRACER;
							GENERIK_TRACE("error in gst_player");
							on_error(message);
							break;
						}
						case GST_MESSAGE_EOS: {
							REM_GENERIK_TRACER;
							GENERIK_TRACE("end of stream reached");
							on_endofstream();
							break;
						}
						case GST_MESSAGE_TAG: {
							REM_GENERIK_TRACER;
							GENERIK_TRACE("received tag metadata");
							on_tag(message);
							break;
						}
						default:
							break;
						}
					}
				}
			}

			static void process_tag(const GstTagList *list, const gchar *tag, gpointer user_data)
			{
				REM_GENERIK_TRACER;
				std::stringstream info;
				std::string& result = *(std::string*)(user_data);
				result = tag; result += " \"";
				info<<"tag: "<<tag<<"; ";
				GType type = gst_tag_get_type(tag);
				switch (type)
				{
					case G_TYPE_STRING:
					{
						gchar* value = 0;
						if (gst_tag_list_get_string(list, tag, &value))
						{
							info<<"value: "<<value<<"; ";
							result += value;
							g_free(value);
						}
					}
					break;
					default:
					{
						info<<"could not process type: "<<g_type_name(type);
					}
					break;
				}
				result += "\"";
				GENERIK_TRACE_VALUE(info.str());
			}
		
			virtual std::string on_tag(GstMessage* message)
			{
				REM_GENERIK_TRACER;
				GstTagList* list = gst_tag_list_new();
				gst_message_parse_tag(message, &list);
				std::string result;
				// call process_tag() here, for each tag in the list
				gst_tag_list_foreach(list, process_tag, &result);
				gst_tag_list_free(list);
				GENERIK_TRACE("received tag: "<<result); 
				return result;
			}
			
			/// error handler
			/** gets called by _update() when an error has occurred.
			This simply sets the state and statemsg.
			Nothing else is done.
			*/
			virtual void on_error(GstMessage* message)
			{
				GENERIK_TRACER;
				_status = GMP_ERROR;
				GError *err;
				gchar *debug;
				gst_message_parse_error(message, &err, &debug);
				_statusmsg = "error ";
				_statusmsg += err->message;
				GENERIK_ERROR(_statusmsg);
				g_error_free(err); g_free(debug);
			}
			/// endofstream handler
			/** gets called by _update() when the player has reached the
			end of the current file. It simply stops playback.
			Derived implementations may override this, for example to query
			the next track from a playlist and continue playback.
			*/
			virtual void on_endofstream()
			{
				GENERIK_TRACER;
				stop();
			}

		};

	}
	
}

#endif

